package com.nineclock.attendance.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.nineclock.attendance.dto.AttendGroupDTO;
import com.nineclock.attendance.dto.AttendPunchDTO;
import com.nineclock.attendance.dto.AttendPunchUserWholeDayDTO;
import com.nineclock.attendance.dto.AttendsDTO;
import com.nineclock.attendance.enums.AttendEnums;
import com.nineclock.attendance.mapper.AttendPunchMapper;
import com.nineclock.attendance.pojo.AttendPunch;
import com.nineclock.attendance.service.AttendGroupService;
import com.nineclock.attendance.service.AttendPunchService;
import com.nineclock.attendance.utils.PunchUtils;
import com.nineclock.common.entity.Result;
import com.nineclock.common.enums.ResponseEnum;
import com.nineclock.common.exception.NcException;
import com.nineclock.common.threadlocal.CurrentUserHolder;
import com.nineclock.common.utils.BeanHelper;
import com.nineclock.common.utils.DateTimeUtil;
import com.nineclock.model.dto.SysCompanyUserDTO;
import com.nineclock.system.feign.SysUserFeign;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import sun.plugin.javascript.navig.LinkArray;

import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AttendPunchServiceImpl implements AttendPunchService {
    @Autowired
    AttendPunchMapper attendPunchMapper;
    @Autowired
    SysUserFeign sysUserFeign;
    @Autowired
    AttendGroupService attendGroupService;

    /**
     * 打卡（封装打卡信息）
     * 1. 转换为打卡对象
     * 2. 获取用户考勤组
     * 3. 判断是否为有效打卡(是否在范围内， 是否为工作日， 是否为有效打卡)
     * 4. 保存打卡信息
     *
     * @param attendPunchDTO
     */
    @Override
    public void punch(AttendPunchDTO attendPunchDTO) {
        if (attendPunchDTO == null) {
            throw new NcException(ResponseEnum.INVALID_PARAM_ERROR);
        }
        AttendPunch attendPunch = BeanHelper.copyProperties(attendPunchDTO, AttendPunch.class);

        attendPunch.setCreateTime(new Date());
        attendPunch.setCompanyId(CurrentUserHolder.get().getCompanyId());
        attendPunch.setCompanyUserId(CurrentUserHolder.get().getCompanyUserId());

        attendPunch.setPunchTime(new Date());
        attendPunch.setPunchDateStr(DateTimeUtil.dateToStr(new Date(), DateTimeUtil.TIME_FORMAT_2));

        attendPunch.setPunchOnOffWork(DateTimeUtil.noonType(new Date()) + 1);

        AttendGroupDTO attendGroupDTO = attendGroupService.getAttendGroupByUserId();

        //判断是否有效打卡-有效范围内
        boolean validatePunchArea = PunchUtils.validatePunchArea(attendPunchDTO, attendGroupDTO);
        attendPunch.setEffectiveValid(validatePunchArea);
        if (!validatePunchArea) {
            throw new NcException(ResponseEnum.PUNCH_INVALID_AREA);
        }
        //是否为工作日
        boolean validateWorkDay = PunchUtils.validateWorkDay(attendGroupDTO);
        if (!validatePunchArea) {
            throw new NcException(ResponseEnum.PUNCH_INVALID_DAY);
        }

        //是否为有效打卡
        boolean validatePunch = this.validatePunch(attendPunch);

        if (validatePunch) {
            PunchUtils.setPunchType(attendPunch, attendGroupDTO);
        }
        attendPunchMapper.insert(attendPunch);
    }

    @Override
    public AttendPunchUserWholeDayDTO queryPunchRecord() {

        AttendPunchUserWholeDayDTO wholeDayDTO = new AttendPunchUserWholeDayDTO();

        Long companyId = CurrentUserHolder.get().getCompanyId();
        Long companyUserId = CurrentUserHolder.get().getCompanyUserId();
        String noStr = DateTimeUtil.dateToStr(new Date(), DateTimeUtil.TIME_FORMAT_2);

        LambdaQueryWrapper<AttendPunch> attendPunchQw = new LambdaQueryWrapper<>();

        attendPunchQw.eq(AttendPunch::getCompanyId, companyId)
                .eq(AttendPunch::getCompanyUserId, companyUserId)
                .eq(AttendPunch::getEffectiveValid, true)
                .eq(AttendPunch::getPunchOnOffWork, AttendEnums.NOON_TYPE_MORNING.value())
                .eq(AttendPunch::getPunchDateStr, noStr);

        AttendPunch attendPunch = attendPunchMapper.selectOne(attendPunchQw);

        //查询下午打卡情况
        LambdaQueryWrapper<AttendPunch> attendPunchLambdaQueryWrapper = new LambdaQueryWrapper<>();
        attendPunchLambdaQueryWrapper.eq(AttendPunch::getCompanyId, companyId)
                .eq(AttendPunch::getCompanyUserId, companyUserId)
                .eq(AttendPunch::getEffectiveValid, true)
                .eq(AttendPunch::getPunchOnOffWork, AttendEnums.NOON_TYPE_AFTERNOON.value())
                .eq(AttendPunch::getPunchDateStr, noStr);
        AttendPunch punch = attendPunchMapper.selectOne(attendPunchLambdaQueryWrapper);
        wholeDayDTO.setAttendPunchMorningResponse(BeanHelper.copyProperties(attendPunch, AttendPunchDTO.class));
        wholeDayDTO.setAttendPunchAfterNoonResponse(BeanHelper.copyProperties(punch, AttendPunchDTO.class));

        return wholeDayDTO;
    }

    /**
     * A. 获取两个日期时间范围之间的日期列表
     * B. 获取当前企业下所有的用户数据
     * C. 查询每个员工每天的打卡情况
     */
    @Override
    public List<AttendPunchDTO> queryMembers(String startTime, String endTime) {

        List<String> spanDateList = this.getSpanDateList(startTime, endTime);
        List<SysCompanyUserDTO> companyUserDTOS = sysUserFeign.queryAllCompanyUser();
        List<AttendPunchDTO> punchDTOList = getAttendPunchDTOS(startTime, endTime, spanDateList, companyUserDTOS);
        return punchDTOList;
    }

    /**
     * 导出指定时间段之内的考勤数据
     * @param startTime
     * @param endTime
     */
    @Override
    public void exportAttendData(String startTime, String endTime) throws IOException {
//获取考勤数据
        List<AttendPunchDTO> punchDTOS = this.queryMembers(startTime, endTime);

        //设置文件下载的头信息
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        response.setHeader("Content-disposition", "attachment;filename=Attendance.xlsx ");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setCharacterEncoding("utf-8");

        //写入Excel
        EasyExcel.write(response.getOutputStream())
                .excelType(ExcelTypeEnum.XLS)
                .head(handleHeader(punchDTOS))
                .registerWriteHandler(new CustomHandler()) //设置列宽
                .registerWriteHandler(horizontalCellStyleStrategy()) //设置水平居中
                .sheet("考勤记录")
                .doWrite(handleData(punchDTOS));
    }
    //内部类,设置列宽
    static class CustomHandler extends AbstractColumnWidthStyleStrategy {

        private static final int MAX_COLUMN_WIDTH = 4500;
        public CustomHandler() {
        }

        @Override
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) {
            boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
            if (needSetWidth) {
                // 所有是head的cell设置固定的宽度 MAX_COLUMN_WIDTH
                writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), MAX_COLUMN_WIDTH);
                // 设置默认行高
                // writeSheetHolder.getCachedSheet().setDefaultRowHeightInPoints();
            }
        }
    }

    //设置样式
    public HorizontalCellStyleStrategy horizontalCellStyleStrategy(){

        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        //内容策略 - 水平居中
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);

        //设置字体
        WriteFont font = new WriteFont();
        font.setFontName("阿里巴巴普惠体");
        font.setFontHeightInPoints((short)11);
        contentWriteCellStyle.setWriteFont(font);

        HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(contentWriteCellStyle, contentWriteCellStyle);

        return horizontalCellStyleStrategy;
    }

    //组装表头信息
    private List<List<String>> handleHeader(List<AttendPunchDTO> punchDTOS){
        List<List<String>> headers = new ArrayList<List<String>>();

        headers.add(Arrays.asList("姓名"));
        headers.add(Arrays.asList("工号"));
        headers.add(Arrays.asList("部门"));
        headers.add(Arrays.asList("职位"));

        //动态表头
        AttendPunchDTO attendPunchDTO = punchDTOS.get(0);
        if(attendPunchDTO != null){
            List<AttendsDTO> attendsList = attendPunchDTO.getAttendsList();
            if(CollectionUtil.isNotEmpty(attendsList)){
                for (AttendsDTO attendsDTO : attendsList) {
                    headers.add(Arrays.asList(attendsDTO.getAttendDate()));
                }
            }
        }

        return headers;
    }

    //组装数据
    private List<List<String>> handleData(List<AttendPunchDTO> punchDTOS){
        List<List<String>> dataList = new ArrayList<List<String>>();

        if(CollectionUtil.isNotEmpty(punchDTOS)){
            for (AttendPunchDTO punchDTO : punchDTOS) {
                //姓名, 工号, 部门, 职位
                List<String> data = new ArrayList<String>();
                data.add(punchDTO.getUserName());
                data.add(punchDTO.getWorkNumber());
                data.add(punchDTO.getDepartmentName());
                data.add(punchDTO.getPost());

                //动态信息 : 每日打卡情况
                List<AttendsDTO> attendsList = punchDTO.getAttendsList();
                if(CollectionUtil.isNotEmpty(attendsList)){
                    for (AttendsDTO dto : attendsList) {
                        data.add(dto.getAttendStatus());
                    }
                }
                dataList.add(data);
            }
        }

        return dataList;

    }

    private List<String> getSpanDateList(String startTime, String endTime) {
        List<Date> dateList = DateTimeUtil.getDatesBetweenTwoDate(startTime, endTime, DateTimeUtil.TIME_FORMAT_2);
        List<String> collect = dateList.stream().map(date -> DateTimeUtil.dateToStr(date, DateTimeUtil.TIME_FORMAT_2)).collect(Collectors.toList());
        return collect;
    }

    private List<AttendPunchDTO> getAttendPunchDTOS(String startTime, String endTime, List<String> spanDateList, List<SysCompanyUserDTO> companyUserDTOS) {
        return companyUserDTOS.stream().map(sysCompanyUserDTO -> {
            AttendPunchDTO attendPunchDTO = BeanHelper.copyProperties(sysCompanyUserDTO, AttendPunchDTO.class);
            LambdaQueryWrapper<AttendPunch> attendPunchLambdaQueryWrapper = new LambdaQueryWrapper<>();
            attendPunchLambdaQueryWrapper.eq(AttendPunch::getCompanyUserId, sysCompanyUserDTO.getId());
            attendPunchLambdaQueryWrapper.eq(AttendPunch::getEffectiveValid, true);
            attendPunchLambdaQueryWrapper.between(AttendPunch::getPunchDateStr, startTime, endTime);

            List<AttendPunch> attendPunches = attendPunchMapper.selectList(attendPunchLambdaQueryWrapper);
            List<AttendsDTO> attendsDTOS = this.handleAttendPunchEverDay(spanDateList, attendPunches);
            attendPunchDTO.setAttendsList(attendsDTOS);
            return attendPunchDTO;
        }).collect(Collectors.toList());
    }

    /**
     * 获取员工打卡信息
     *
     * @param spanDateList
     * @param attendPunches
     * @return
     */
    private List<AttendsDTO> handleAttendPunchEverDay(List<String> spanDateList, List<AttendPunch> attendPunches) {
        Map<String, List<AttendPunch>> punchMap = attendPunches.stream().collect(Collectors.groupingBy(AttendPunch::getPunchDateStr));
        //获取每一天的打卡情况
        List<AttendsDTO> attendsDTOS = spanDateList.stream().map(dateStr -> {
            AttendsDTO attendsDTO = new AttendsDTO();
            attendsDTO.setAttendDate(dateStr);

            List<AttendPunch> punches = punchMap.get(dateStr);
            if (CollectionUtil.isEmpty(punches)) {
                attendsDTO.setAttendStatus("缺卡/缺卡");
            } else {
                attendsDTO.setAttendStatus(this.handleAttendStatus(punches));
            }
            return attendsDTO;
        }).collect(Collectors.toList());

        return attendsDTOS;
    }

    private String handleAttendStatus(List<AttendPunch> punches) {

        String amAttendStatus = "缺卡";//上午
        String pmAttendStatus = "缺卡";//下午
        for (AttendPunch punch : punches) {
            if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_MORNING.value()) {
                amAttendStatus = this.getPushDesc(punch.getMorningPunchType());
            } else if (punch.getPunchOnOffWork() == AttendEnums.NOON_TYPE_AFTERNOON.value()) {
                pmAttendStatus = this.getPushDesc(punch.getAfternoonPunchType());
            }
        }
        return amAttendStatus + "/" + pmAttendStatus;
    }

    private String getPushDesc(Integer morningPunchType) {
        String desc = "";
        switch (morningPunchType) {
            case 1:
                desc = AttendEnums.PUNCH_TYPE_OK.toString();
                break;
            case 2:
                desc = AttendEnums.PUNCH_TYPE_LATE.toString();
                break;
            case 3:
                desc = AttendEnums.PUNCH_TYPE_EARLY.toString();
                break;
            case 4:
                desc = AttendEnums.PUNCH_TYPE_STAYALAY.toString();
                break;
            case 5:
                desc = AttendEnums.PUNCH_TYPE_NOT_WORK_DAY.toString();
                break;
            default:
                break;
        }
        return desc;

    }


    /**
     * 1. 查询打卡记录，如果没有查询到，无论何时打卡都是有效的
     * 2. 查询到了
     * 如果是上午打卡， 则无效
     * 如果是下午打卡， 以最后一次打卡记录为准, 之前打卡记录无效
     *
     * @param attendPunch
     * @return
     */
    private boolean validatePunch(AttendPunch attendPunch) {
        LambdaQueryWrapper<AttendPunch> punchQw = new LambdaQueryWrapper<>();
        punchQw.eq(AttendPunch::getPunchOnOffWork, attendPunch.getPunchOnOffWork());
        punchQw.eq(AttendPunch::getCompanyId, CurrentUserHolder.get().getCompanyId());
        punchQw.eq(AttendPunch::getCompanyUserId, CurrentUserHolder.get().getCompanyUserId());
        punchQw.eq(AttendPunch::getPunchDateStr, attendPunch.getPunchDateStr());

        Integer count = attendPunchMapper.selectCount(punchQw);
        if (count == 0) {
            attendPunch.setEffectiveValid(true);
        } else {
            if (attendPunch.getPunchOnOffWork() == 1) {
                attendPunch.setEffectiveValid(false);
            } else {
                attendPunch.setEffectiveValid(true);
                AttendPunch updateAttendPunch = new AttendPunch();
                updateAttendPunch.setEffectiveValid(false);
                attendPunchMapper.update(updateAttendPunch, punchQw);


            }
        }
        return attendPunch.getEffectiveValid();
    }

    @Override
    public String generateExcelReportByCompanyId(Long companyId) throws IOException {
        //1. 获取本月的第一天及最后一天
        String firstDay = DateTimeUtil.getCurrentMonthFirstDay();
        String lastDay = DateTimeUtil.getCurrentMonthLastDay();

        List<String> spanDateList = this.getSpanDateList(firstDay, lastDay); //获取起止日期之间所有的日期集合

        //2. 获取企业ID对应的员工信息
        Result<List<SysCompanyUserDTO>> listResult = sysUserFeign.queryCompanyUser(companyId, null);
        List<SysCompanyUserDTO> userDTOList = listResult.getData();
        if(CollectionUtil.isEmpty(userDTOList)){
            return null;
        }

        //3. 获取员工的考勤打卡数据
        List<AttendPunchDTO> attendPunchDTOS = this.getAttendPunchDTOS(firstDay, lastDay, spanDateList, userDTOList);


        //4. 生成Excel
        String foldername = "D:/考勤数据/"+companyId;
        String filename = foldername +"/"+userDTOList.get(0).getCompanyName()+"-"+firstDay.substring(0,7)+"-考勤数据.xlsx"; //2021-02-05

        if(!new File(foldername).exists()){
            new File(foldername).mkdirs();
        }

        EasyExcel.write(filename)
                .excelType(ExcelTypeEnum.XLSX)
                .head(handleHeader(attendPunchDTOS))
                .registerWriteHandler(new CustomHandler())
                .registerWriteHandler(horizontalCellStyleStrategy())
                .sheet("考勤数据")
                .doWrite(handleData(attendPunchDTOS));

        return filename;
    }
}
